package cn.gzmli.dtt.common.utils;

import org.springframework.web.multipart.MultipartFile;

import java.io.*;
import java.math.BigDecimal;
import java.util.HashMap;
import java.util.Map;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

/**
 * @author Pyj
 * @date 2019-08-01
 * @desc 文件工具类
 * 处理上传附件, 校验是否合法
 * 在服务器端判断文件类型的问题，故用获取文件头的方式，
 * 直接读取文件的前几个字节，来判断上传文件是否符合格式
 */
public class FileUtils {
    /********************************************** 文件上传基于文件流安全校验 解决方案 Start **********************************************/
    //记录各个文件头信息及对应的文件类型
    public static Map<String, String> mFileTypes = new HashMap<String, String>();

    //常见文件类型定义
    static {
        //图片类
        mFileTypes.put("FFD8FFE1", ".jpg,.jpeg"); //JPEG,JPG
        mFileTypes.put("FFD8FFE0", ".jpg,.jpeg"); //JPEG,JPG
        mFileTypes.put("89504E47", ".png"); //PNG
        mFileTypes.put("47494638", ".gif"); //GIF
        mFileTypes.put("49492A00", ".tif"); //TIFF
        mFileTypes.put("424D", ".bmp"); //Windows Bitmap
        //PS和CAD
        mFileTypes.put("38425053", ".psd"); //PSD
        mFileTypes.put("41433130", ".dwg"); //CAD
        mFileTypes.put("252150532D41646F6265",".ps"); //PS
        /**
         * docx,xlsx,pptx和zip相同 doc和xls以及ppt相同
         */
        //办公文档类
        mFileTypes.put("D0CF11E0", ".office,.doc,.xls,.ppt"); //office类型,包括doc、xls和ppt
        //压缩包
        mFileTypes.put("504B0304", ".zip,.docx,.xlsx,.pptx"); //ZIP
        mFileTypes.put("52617221", ".rar"); //RAR
        mFileTypes.put("25504446", ".pdf"); //PDF
        mFileTypes.put("7B5C7274", ".rtf"); //日记本(Rich Text Format)

        //mFileTypes.put("D0CF11E0", ".doc"); //ppt、doc、xls
        //mFileTypes.put("504B0304", ".docx"); //pptx、docx、xlsx
        //mFileTypes.put("D0CF11E0", ".xls");
        //mFileTypes.put("504B0304", ".xlsx");
        //mFileTypes.put("D0CF11E0", ".ppt");
        //mFileTypes.put("504B0304", ".pptx");

        /**注意由于文本文档录入内容过多，则读取文件头时较为多变-START**/
        mFileTypes.put("0D0A0D0A", ".txt"); //txt
        mFileTypes.put("0D0A2D2D", ".txt"); //txt
        mFileTypes.put("0D0AB4B4", ".txt"); //txt
        mFileTypes.put("B4B4BDA8", ".txt"); //文件头部为汉字
        mFileTypes.put("73646673", ".txt"); //txt,文件头部为英文字母
        mFileTypes.put("32323232", ".txt"); //txt,文件头部内容为数字
        mFileTypes.put("0D0A09B4", ".txt"); //txt,文件头部内容为数字
        mFileTypes.put("3132330D", ".txt"); //txt,文件头部内容为数字
        /**注意由于文本文档录入内容过多，则读取文件头时较为多变-END**/

        //视频或音频类
        mFileTypes.put("3026B275",".wma");
        mFileTypes.put("57415645", ".wav");
        mFileTypes.put("41564920", ".avi");
        mFileTypes.put("4D546864", ".mid");
        mFileTypes.put("2E524D46", ".rm");
        mFileTypes.put("000001BA", ".mpg");
        mFileTypes.put("000001B3", ".mpg");
        mFileTypes.put("6D6F6F76", ".mov");
        mFileTypes.put("3026B2758E66CF11", ".asf");
        //程序文件
        mFileTypes.put("3C3F786D6C", ".xml"); //XML
        mFileTypes.put("68746D6C3E", ".html"); //HTML
        mFileTypes.put("46726F6D", ".mht");
        mFileTypes.put("4D494D45", ".mhtml");
        mFileTypes.put("7061636B", ".java");
        mFileTypes.put("3C254020", ".jsp");
        mFileTypes.put("4D5A9000", ".exe");
        mFileTypes.put("44656C69766572792D646174653A", ".eml"); //Email邮件
        mFileTypes.put("5374616E64617264204A", ".mdb"); //Access数据库文件

        mFileTypes.put("4F5F9000", ".apk");
        mFileTypes.put("0xD0CF11E0A1B11AE1", ".msi");


    }

    /**
     * 根据文件的输入流获取文件头信息
     *
     * @param is 文件流
     * @return 文件头信息
     */
    public static String getFileType(InputStream is) {
        byte[] b = new byte[4];
        if(is != null){
            try {
                is.read(b, 0, b.length);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        //根据文件转换成的字节数组获取文件头信息
        return mFileTypes.get(bytesToHexString(b));
    }

    /**
     * 将要读取文件头信息的文件的byte数组转换成string类型表示
     * 将字节数组的前四位转换成16进制字符串，并且转换的时候，要先和0xFF做一次与运算。
     * @param src 要读取文件头信息的文件的byte数组
     * @return 文件头信息
     */
    private static String bytesToHexString(byte[] src) {
        StringBuilder builder = new StringBuilder();
        if (src == null || src.length <= 0) {
            return null;
        }
        String hv;
        for (int i = 0; i < src.length; i++) {
            // 以十六进制（基数 16）无符号整数形式返回一个整数参数的字符串表示形式，并转换为大写
            hv = Integer.toHexString(src[i] & 0xFF).toUpperCase();
            if (hv.length() < 2) {
                builder.append(0);
            }
            builder.append(hv);
        }

        System.out.println("获取文件头信息:"+builder.toString());

        return builder.toString();
    }

    /**
     * 判断上传的文件是否合法
     * 第一：检查文件的扩展名;
     * 第二：检查文件的MIME类型 。
     * @param files
     * @return boolean
     */
    public static boolean getUpFileLegitimacyFlag(MultipartFile files) {
        boolean upFlag = false;//true为真表示符合上传条件，false为假表示不符合
        if(files != null){
            String fileName =files.getOriginalFilename();

            System.out.println("#######上传的文件名称:"+fileName);

            if(!"".equals(fileName) && fileName != null) {

                /**返回在此字符串中最右边出现的指定子字符串的索引   **/
                String suffixName = fileName.substring(fileName.lastIndexOf("."));

                /**统一转换为小写**/
                suffixName = suffixName.toLowerCase();

                /**将所有合法的文件后缀集合转换成字符串**/
                String res_fileType = filesExtToString(mFileTypes);

                /**第一步：检查文件扩展名，是否符合要求范围**/
                if(res_fileType.indexOf(suffixName) != -1){
                    upFlag=true;
                }

                /**
                 * 第二步：获取上传附件的文件头，判断属于哪种类型,并获取其扩展名
                 * 直接读取文件的前几个字节，来判断上传文件是否符合格式
                 * 防止上传附件变更扩展名绕过校验
                 ***/
                if(upFlag){

                    byte[] b = new byte[4];

                    String req_fileType = null;
                    try {
                        req_fileType = getFileType(files.getInputStream());
                    } catch (IOException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }
                    System.out.println("#######上传的文件类型:"+req_fileType);

                    /**第三步：检查文件扩展名，是否符合要求范围**/
                    if(req_fileType != null && !"".equals(req_fileType) && !"null".equals(req_fileType)){
                        /**第四步：校验上传的文件扩展名，是否在其规定范围内**/
                        if(res_fileType.indexOf(req_fileType) != -1){
                            upFlag=true;
                        }else{
                            upFlag=false;
                        }
                    }else{
                        /**特殊情况校验,如果用户上传的扩展名为,文本文件,则允许上传-START**/
                        if(suffixName.indexOf(".txt") != -1){
                            upFlag=true;
                        }else{
                            upFlag=false;
                        }
                        /**特殊情况校验,如果用户上传的扩展名为,文本文件,则允许上传-END**/
                    }
                }
            }
        }
        return upFlag;
    }

    /**
     * 获取文件扩展名集合字符串
     *
     * @param fileTypeMaps
     * @return String
     */
    public static String filesExtToString(Map<String,String> fileTypeMaps) {
        String resList;
        StringBuilder sb = new StringBuilder();

        //获取Map中key和value的方法一
        /*for (String key: mFileTypes.keySet()) {
            String value = mFileTypes.get(key);
            sb.append(value);
        }*/
        //获取Map中key和value的方法二(性能较高)
        for(Map.Entry<String, String> vo : mFileTypes.entrySet()) {
            sb.append(vo.getKey()+"_"+vo.getValue()+",");
        }
        //截取尾号,字符串
        resList = sb.substring(0, sb.length()-1).toString();
        System.out.println("获取文件扩展名集合字符串："+resList);
        return resList;
    }

    /********************************************** 文件上传基于文件流安全校验 解决方案 End **********************************************/

    /**
     * @param inputFileName 你要压缩的文件夹(整个完整路径)
     * @param zipFileName 压缩后的文件(整个完整路径)
     */
    public static void fileZip(String inputFileName, String zipFileName) throws Exception {
        //定义输入文件流
        File inputFile = new File(inputFileName);
        //定义压缩输出流
        ZipOutputStream out = new ZipOutputStream(new FileOutputStream(zipFileName));
        toZip(out, inputFile, "");
        out.flush();
        out.close();
    }

    private static void toZip(ZipOutputStream out, File f, String base) throws Exception {
        if (f.isDirectory()) {
            File[] fl = f.listFiles();
            out.putNextEntry(new ZipEntry(base + "/"));
            base = base.length() == 0 ? "" : base + "/";
            for (int i = 0; i < fl.length; i++) {
                toZip(out, fl[i], base + fl[i].getName());
            }
        } else {
            out.putNextEntry(new ZipEntry(base));
            FileInputStream in = new FileInputStream(f);
            int b;
            //System.out.println(base);
            while ((b = in.read()) != -1) {
                out.write(b);
            }
            in.close();
        }
    }

    /**
     * 创建目录
     *
     * @param destDirName 目标目录名
     * @return 目录创建成功返回true，否则返回false
     */
    public static boolean createFileDir(String destDirName) {
        File dir = new File(destDirName);
        if (dir.exists()) {
            return false;
        }
        if (!destDirName.endsWith(File.separator)) {
            destDirName = destDirName + File.separator;
        }
        // 创建单个目录
        if (dir.mkdirs()) {
            return true;
        } else {
            return false;
        }
    }

    /**
     * 删除文件路径下所有文件和文件夹
     * @param folderPath 文件路径 (只删除此路径的最末路径下所有文件和文件夹)
     */
    public static void delFolder(String folderPath) {
        try {
            delAllFile(folderPath); 	// 删除完里面所有内容
            String filePath = folderPath;
            filePath = filePath.toString();
            File myFilePath = new File(filePath);
            myFilePath.delete(); 		// 删除空文件夹
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 删除指定文件夹下所有文件
     * @param path 文件夹完整绝对路径
     */
    public static boolean delAllFile(String path) {
        boolean flag = false;
        File file = new File(path);
        if (!file.exists()) {
            return flag;
        }
        if (!file.isDirectory()) {
            return flag;
        }
        String[] tempList = file.list();
        File temp = null;
        for (int i = 0; i < tempList.length; i++) {
            if (path.endsWith(File.separator)) {
                temp = new File(path + tempList[i]);
            } else {
                temp = new File(path + File.separator + tempList[i]);
            }
            if (temp.isFile()) {
                temp.delete();
            }
            if (temp.isDirectory()) {
                delAllFile(path + "/" + tempList[i]);	// 先删除文件夹里面的文件
                delFolder(path + "/" + tempList[i]);	// 再删除空文件夹
                flag = true;
            }
        }
        return flag;
    }

    /**
     * 删除目录下的所有子目录和文件
     *
     * @param filepath
     * @throws IOException
     */
    protected static void deleteDirs(String filepath) throws IOException {
        File f = new File(filepath);// 定义文件路径
        if (f.exists()) {// 判断是否存在
            if (f.isDirectory()) {// 判断是文件还是目录
                if (f.listFiles().length == 0) {// 若目录下没有文件则直接删除
                    f.delete();// 删除目录
                } else {// 若有则把文件放进数组，并判断是否有下级目录
                    File delFile[] = f.listFiles();
                    int i = f.listFiles().length;
                    for (int j = 0; j < i; j++) {
                        if (delFile[j].isDirectory()) {
                            deleteDirs(delFile[j].getAbsolutePath());// 递归调用delDirs方法并取得子目录路径
                        } else {
                            delFile[j].delete();// 删除文件
                        }
                    }
                    f.delete();// 删除目录
                }
            } else {
                f.delete();// 删除文件
            }
        }
    }

    /**
     * 删除单个文件
     *
     * @param file
     * @return
     * @throws IOException
     */
    public static boolean deleteFile(File file) throws IOException {
        boolean flag = false;
        if (file.exists() && file.isFile()) {
            file.delete();
            flag = true;
        }
        return flag;
    }

    /**
     * MultipartFile转File
     * @param multipartFile
     * @return file
     * @throws Exception
     */
    public static File multipartToFile(MultipartFile multipartFile) throws Exception {
        // 创建文件
        File f = new File(multipartFile.getOriginalFilename());
        try {
            // 得到文件流。以文件流的方式输出到新文件
            InputStream is  = multipartFile.getInputStream();
            OutputStream os = new FileOutputStream(f);
            // 可以使用byte[] b = multipartFile.getBytes();代替while
            byte[] buffer = new byte[8192];
            int n = 0;
            while ((n = is.read(buffer,0,8192)) != -1){
                os.write(buffer, 0, n);
            }
            os.close();
            is.close();
        } catch (IOException e){
            e.printStackTrace();
        }
        return f;
    }

    private static final long KB = 1024;// KB
    private static final long MB = KB * KB;// MB
    private static final long GB = KB * MB;// GB

    /**
     * 处理文件大小
     */
    public static String fileSize(long fileSize) {
        if (fileSize <= 0) {
            return "";
        } else if (fileSize < MB) {
            BigDecimal b = new BigDecimal((double) fileSize / KB);
            return b.setScale(2, BigDecimal.ROUND_HALF_UP).doubleValue() + "K";
        } else if (fileSize < GB) {
            BigDecimal b = new BigDecimal((double) fileSize / MB);
            return b.setScale(2, BigDecimal.ROUND_HALF_UP).doubleValue() + "M";
        } else {
            BigDecimal b = new BigDecimal((double) fileSize / GB);
            return b.setScale(2, BigDecimal.ROUND_HALF_UP).doubleValue() + "G";
        }
    }

    /**
     * 主函数入口调用
     * @param args
     */
    public static void main(String[] args) throws Exception {
        String filePath = "E:/IMP/files/2019-08-05/";
        String fileName = "55cf99bad052499d8c53ebbb3e97694b_.png";
        File sorucFile = new File(filePath);
        File destFile = new File(filePath+"\\cut"+"\\cut_"+fileName);
        File f = new File(filePath);
        System.out.println("size="+f.length());

    }

}
